Repository navigation
Conversation
Reuse integration teardown to remove generated commands, native event entries, and the last managed dispatcher before cleaning project artifacts. Add explicit constitution/specs preservation options, ownership-aware recovery, declared global-scope handling, and positive and negative lifecycle coverage. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Confirmed deletion-scope, preservation, and recovery defects prevent safe teardown.
7 open issues
Add Ruff E402 suppression to the late root-command import · New Validate both dotted and formatted command names before cleanup · New Exclude preserved constitution claims before deleting tracked symlinks · New Handle Claude uninstall when only the init options inventory exists · New Validate modified owned files during destroy planning · New Retain cleanup registries until package deletion completes · New Guard ownership rewrites when adapter manifests are missing · New
What changed in this PR
Adds specify destroy as the project-uninstall counterpart to specify init, reusing integration teardown before removing Spec Kit infrastructure.
Changes:
- Adds preview, confirmation, structured JSON, and artifact-preservation options.
- Extracts shared integration uninstall logic and adds contribution-path and native event-cleanup validation.
- Documents deletion boundaries and expands lifecycle, preservation, recovery, and external-adapter coverage.
Validation used isolated standard-library checks; project tests and lint were not run.
AI review by GitHub Copilot for the requesting reviewer (model unavailable; automated mode, default settings), covering analysis and suggested fixes.
| File | Description |
|---|---|
tests/specify_cli/test_operation_destroy.py |
Covers ownership, preservation, deletion scope, and failure recovery. |
tests/specify_cli/test_command_init.py |
Updates expected root-command registration order. |
tests/specify_cli/test_command_destroy.py |
Tests confirmation, preservation flags, and JSON contracts. |
tests/specify_cli/integrations/test_operation_uninstall.py |
Tests the shared uninstall operation. |
tests/specify_cli/integrations/test_installed_adapters.py |
Adds external-adapter teardown and preservation coverage. |
src/specify_cli/mcp_destroy.py |
Records destroy as unavailable through MCP. |
src/specify_cli/integrations/command_uninstall.py |
Delegates CLI uninstall to the shared operation. |
src/specify_cli/integrations/_operation_uninstall.py |
Centralizes teardown, recovery, and package cleanup. |
src/specify_cli/integrations/_lifecycle.py |
Removes uninstall-specific orchestration moved to the shared operation. |
src/specify_cli/events/__init__.py |
Adds native event-cleanup verification. |
src/specify_cli/command_destroy.py |
Implements command options, preview, confirmation, and output. |
src/specify_cli/agents.py |
Enumerates recorded contribution output paths. |
src/specify_cli/_operation_destroy.py |
Plans, validates, and executes project teardown. |
src/specify_cli/__init__.py |
Registers the destroy command. |
docs/reference/overview.md |
Introduces project uninstall in the command overview. |
docs/reference/core.md |
Documents options, deletion boundaries, and recovery behavior. |
Review effort: Max
Preflight all contribution cleanup names and dispatcher ownership/content, preserve symlinked constitution claims lexically, reject incomplete legacy inventories before mutation, retain package registries through purge failures, and allow ordinary external uninstall without a manifest. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed the review in b7b7297. Cleanup now preflights dotted and formatted command paths, preserves symlinked constitution claims by lexical manifest key, rejects legacy-only inventories during preview with migration guidance, checks dispatcher ownership/content before any uninstall, retains extension/preset registries until package deletion completes, and permits ordinary external uninstall when its manifest is missing. Added regression coverage, including purge retry and regenerated LF/CRLF dispatchers. The exact CI command On behalf of @mnriem: GitHub Copilot, GPT-6.1 Sol ( |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved preservation and teardown-recovery defects can cause data loss or leave cleanup incomplete.
9 open issues
Preserve constitution using filesystem-aware identity · New Force cleanup of validated whole-owned extension paths · New Exclude preserved constitution claims before deleting tracked symlinks Resolve ownership across stacked preset skill claims · New Validate native settings before destructive teardown · New Validate dispatcher cleanup against the last claimant · New Synchronize preset hashes before destroy planning · New Delete specs before removing infrastructure · New Render structured destroy error recovery details · New
6 resolved since last review
Validate both dotted and formatted command names before cleanup Add Ruff E402 suppression to the late root-command import Guard ownership rewrites when adapter manifests are missing Retain cleanup registries until package deletion completes Validate modified owned files during destroy planning Handle Claude uninstall when only the init options inventory exists
Review effort: Max
Handle case-insensitive artifact preservation without following links, force validated generic contribution cleanup before dropping integration state, resolve stacked preset skill provenance, preflight native settings and the final dispatcher claimant, synchronize hashes after managed preset initialization, finish specs-directory deletion before infrastructure, and expose human recovery details. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed the preservation and cleanup findings in d5bf78d. Constitution retention now uses filesystem-aware entry identity without following leaf links, including alternate case spellings and tracked symlinks. Validated generic contribution cleanup honors force and verifies completion before dropping integration state. Stacked preset skill claims share the lifecycle's validated override provenance; preset initialization updates hashes only for files changed by managed rendering. Planning parses built-in native settings and checks the dispatcher against its last claimant before any teardown. Specs files and directories are removed before infrastructure, and human-readable errors include remaining paths and completed work. Regression cases reproduced all eight new behavioral findings before fixes; additional tests cover retry and preservation boundaries. The full local suite passed with 13,279 passed and 128 skipped; CI-pinned Ruff and the real workflow smoke (20 runs, ten resumes) also pass. New-head CI is green: Ruff and all six Ubuntu/macOS/Windows Python 3.13/3.14 test jobs passed, as did Markdown lint, ShellCheck, CodeQL, and dependency checks. Review threads are left open for confirmation. On behalf of @mnriem: GitHub Copilot, GPT-6.1 Sol ( |
There was a problem hiding this comment.
🔵 Needs a closer look
Destructive cleanup spans project and home-scoped files, restructures the uninstall lifecycle, and retains unresolved ownership and recovery failures.
12 open issues
Avoid reading arbitrary files during path validation · New Validate preset IDs before resolving provenance · New Use Hermes home skill paths for inventory and cleanup · New Distinguish legacy preset skills from explicit ownership claims · New Report completed integration deletions on failure · New Validate SPECIFY_INIT_DIR through the shared override resolver · New Refresh hashes after preset add changes · New Distinguish dangling symlinks from absent files · New Preserve registrations for skipped contribution cleanup · New Restore installation state after late cleanup failure · New Convert teardown SystemExit into reported uninstall errors · New Synchronize preset hashes before destroy planning
8 resolved since last review
Force cleanup of validated whole-owned extension paths Preserve constitution using filesystem-aware identity Exclude preserved constitution claims before deleting tracked symlinks Render structured destroy error recovery details Delete specs before removing infrastructure Validate dispatcher cleanup against the last claimant Validate native settings before destructive teardown Resolve ownership across stacked preset skill claims
Review effort: Max
| changed = False | ||
| for name, previous in before_preset.items(): | ||
| path = project_path / name | ||
| if ( | ||
| path.is_file() and not path.is_symlink() | ||
| and hashlib.sha256(path.read_bytes()).hexdigest() != previous | ||
| ): | ||
| manifest.record_existing(name) | ||
| changed = True | ||
| if changed: | ||
| manifest.save() |
Validate preset provenance before reads, inventory real home-scoped outputs, retain strict cleanup registrations and recovery state, and report completed deletions on failures. Refresh tracked hashes only for managed preset writes and normalize teardown exits and project overrides. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed the 12 open items from this review in 4a6436d. Strict cleanup now retains contribution registrations and restores ownership manifests, installation state, and init options for retry; partial failures report completed project and home-scoped deletions, and teardown exits become structured errors. Inventory no longer reads arbitrary payloads, preset IDs and manifests are validated before provenance reads, home-scoped and legacy preset skills use the correct ownership rules, managed preset writes refresh tracked hashes during both init and later installs, and SPECIFY_INIT_DIR uses the shared resolver. On behalf of @mnriem: generated by GitHub Copilot using GPT-6.1 Sol in autonomous mode (reasoning setting not exposed); AI assistance covered implementation, regression tests, and this comment. |
There was a problem hiding this comment.
🟡 Changes recommended
Hash refresh can record partial outputs as managed baselines and mask registration failures, while preset removal leaves restored skills with stale hashes that interfere with cleanup.
4 open issues
10 resolved since last review
Convert teardown SystemExit into reported uninstall errors Restore installation state after late cleanup failure Preserve registrations for skipped contribution cleanup Distinguish dangling symlinks from absent files Validate SPECIFY_INIT_DIR through the shared override resolver Report completed integration deletions on failure Distinguish legacy preset skills from explicit ownership claims Use Hermes home skill paths for inventory and cleanup Validate preset IDs before resolving provenance Avoid reading arbitrary files during path validation
Review effort: Max
Preserve registration failures and prior hash baselines by refreshing only after successful handlers. Track preset skill restoration and reconciliation so removal round trips do not require forced cleanup. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed the remaining hash-lifecycle feedback in d1142fb. Manifest hashes now refresh only after a rendering handler succeeds, leaving failed handlers’ original errors and previous baselines intact. Preset removal also tracks skill restoration and reconciliation, keeping hashes current for subsequent destroy or integration uninstall without adopting untouched user edits. On behalf of @mnriem: generated by GitHub Copilot using GPT-6.1 Sol in autonomous mode (reasoning setting not exposed); AI assistance covered implementation, regression tests, documentation, and this comment. |
There was a problem hiding this comment.
🔵 Needs a closer look
Human assessment is needed because teardown and registration failure paths can leave partial deletions, orphaned command files, or lost ownership evidence that prevents safe cleanup retries.
1 open issue
3 resolved since last review
Review effort: Max
Bring in the upstream integration lifecycle and regression tests used by the PR merge checks before fixing their managed-hash compatibility failures. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reuse the integration lifecycle snapshot and resync helpers so managed preset rendering refreshes only previously unmodified claims and preserves unchanged recovery markers. Align Claude and Vibe upgrade coverage and docs with managed preset output while retaining refusal and force coverage for genuine user edits. Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed the remaining preset-hash feedback and CI regressions in bd7e746, after synchronizing the branch with upstream main. Shared preset rendering now reuses the integration lifecycle snapshot and resync policy: only previously unmodified, changed outputs receive new hashes, while earlier user edits and unchanged recovered claims retain their ownership evidence. Claude and Vibe upgrade coverage and documentation now distinguish managed preset output from genuine user edits, which still require explicit force to overwrite. Fresh CI is running on this commit. On behalf of @mnriem: generated by GitHub Copilot using GPT-6.1 Sol in autonomous mode (reasoning setting not exposed); AI assistance covered implementation, regression coverage, documentation, and this comment. |
There was a problem hiding this comment.
🟡 Changes recommended
Preflight and teardown disagree on infrastructure hashes and legacy settings ownership, allowing predictable failures after files or integration state have already been removed.
7 open issues
Preflight infrastructure claims before removing integrations · New Preserve native configs with legacy ownership metadata · New Recognize case-insensitive specs paths during preserve-specs planning · New Validate parent paths before skipping missing tracked files · New Include core Hermes outputs in the global file inventory · New Handle confirmation aborts as cancellation · New Refresh hashes after preset add changes
Review effort: Max
| if relative.startswith(".specify/"): | ||
| continue |
| dispatcher_owners[key] = (mode, manifest.is_recovered(name), digest) | ||
| if relative.startswith(".specify/"): | ||
| continue | ||
| if mode != "whole": |
| relative.parts[0].casefold() == "specs" | ||
| and not (allow_specs and relative.parts[0] == "specs") | ||
| ) | ||
| ): | ||
| raise ValueError(f"Unsafe destroy path: {text}") | ||
| return text |
| try: | ||
| mode = path.lstat().st_mode | ||
| except FileNotFoundError: | ||
| return leaves, directories | ||
| # Validate all ancestors through the helper before inspecting the leaf. | ||
| parent = path.parent | ||
| while parent != files.root: | ||
| if parent.is_symlink(): | ||
| raise FileHelperError(f"Refusing destroy through symlinked directory: {parent}") | ||
| if getattr(parent.lstat(), "st_file_attributes", 0) & stat.FILE_ATTRIBUTE_REPARSE_POINT: | ||
| raise FileHelperError(f"Unsupported reparse point: {parent}") | ||
| parent = parent.parent |
| global_files: set[str] = set() | ||
| if global_paths: | ||
| home_files = FileHelper(Path.home(), allow_symlinks=files.allow_symlinks) | ||
| for path in sorted(global_paths): |
| confirmed = typer.confirm( | ||
| "Delete the planned files and directories? " | ||
| "Unpreserved content in .specify/ and specs/ will be lost.", | ||
| default=False, | ||
| ) | ||
| if not confirmed: | ||
| console.print("Uninstall cancelled; no files changed.") | ||
| return |


Description
Add
specify destroyto uninstall Spec Kit from a project without manuallyfinding generated integration files.
operation also used by
specify integration uninstall. Preserve nativeuser settings while removing managed event entries and releasing the last
dispatcher consumer.
.specify/and literal project-localspecs/by default.--preserve-constitutionand--preserve-specsretain their respective SDDartifacts;
--preserve-allcombines both. Force never overrides preservation.external-adapter ownership/recovery rules, and retain original manifest
evidence when strict teardown fails.
project files. Never follow a feature-directory override to enlarge deletion.
This deliberately changes no core prompt/command templates and adds no runtime
dependencies. The constitution/specification deletion scope is explicitly
described in CLI help and documentation.
Testing
uv run specify --helpuv sync && uv run pytestThe exact bare test command in the template was not used. Per the documented
worktree-environment guidance, dependencies and tests used this checkout's
own virtualenv instead:
uv sync --extra test— passed.LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest -n 4 -q— passed:13,246 passed, 128 skipped, 62 warnings on macOS/Python 3.13.17.
ruff check --output-format concise src/specify_cli/_operation_destroy.py src/specify_cli/command_destroy.py src/specify_cli/mcp_destroy.py src/specify_cli/integrations/_operation_uninstall.py src/specify_cli/integrations/command_uninstall.py tests/specify_cli/test_operation_destroy.py tests/specify_cli/test_command_destroy.py tests/specify_cli/integrations/test_operation_uninstall.py— passed. Existing tracked files have no new Ruff diagnostic fingerprints
compared with the committed baseline; unrelated repository-wide lint
findings remain.
git diff --check— passed.Sample-project validation used temporary projects and isolated homes, not the
development checkout:
command/skill layouts, and Hermes; five preservation choices; force on/off.
repeat, retained-byte verification, and external feature-directory isolation.
specify workflow add <smoke-package> --dev,specify workflow run destroy-smoke --input python=<interpreter> --json,and
specify workflow status <run-id> --json— passed.Ten were resumed with
specify workflow resume <run-id> --input approval=approve --jsonandcompleted; ten remained paused before destruction. All preservation choices
and force modes were checked after workflow-generated constitution/specs,
source files, and persisted run state existed.
The workflow smoke used real deterministic shell steps and an approval gate;
it did not mock execution or invoke the bundled AI-driven full SDD cycle.
The workflow generated artifacts, paused, resumed without replaying the
completed step, and verified completion before cleanup. Workflow state and
installed tooling were removed; selected SDD artifacts and unrelated source
were retained byte-for-byte.
Before/after evidence includes the initially missing destroy command, tracked
constitution loss, same-named skill ownership, missing recovery metadata
after teardown failures, global contribution discovery, and preservation flag
parsing/defaults. Regression tests cover the corrected positive and negative
paths. Collection increased from 13,141 at feature start to 13,374; no existing
tests were removed. Cross-platform CI has not been run locally.
AI Disclosure
AI disclosure: Authored on behalf of @mnriem with GitHub Copilot, powered by
GPT-6.1 Sol (
gpt-6.1-sol), in autonomous/agent mode with runtime-defaultreasoning settings and user-directed requirements and iterative feedback.
AI assistance covered feature specification/planning, implementation,
documentation, tests, review/reproduction, executable smoke checks, and this
PR description. The reported automated checks were run by the agent; no claim
of independent human line-by-line review or manual testing is made.